//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace TridentExecutorTray
{
    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.Drawing;
    using System.Threading;
    using System.Windows;
    using System.Windows.Forms;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Research.eResearch.Common.Logging;
    using Microsoft.Research.eResearch.TridentScheduleCommon;
    using Microsoft.Research.eResearch.WFServiceLibrary;
    using Microsoft.VisualBasic.ApplicationServices;

    /// <summary>
    /// Hosts the Executor in the Windows tray and monitors for new job to be run.
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Member Data

        /// <summary>
        /// Holds the default font name
        /// </summary>
        private const string DEFAULT_FONT = "Segoe UI";

        /// <summary>
        /// Workflow process name
        /// </summary>
        private const string WORKFLOW_PROCESS_NAME = "WFApp";

        /// <summary>
        /// Attempts count to connect to the database server
        /// </summary>
        private const string CONFIGURATION_ATTEMPT_COUNT = "SQLReconnectAttempts";

        /// <summary>
        /// Balloon tool tip timeout
        /// </summary>
        private const int BALLOON_TOOLTIP_TIMEOUT = 10;

        /// <summary>
        /// Scheduler monitors the database for new jobs to be run
        /// </summary>
        private WFServiceScheduler scheduler;

        /// <summary>
        /// Thread which hosts the scheduler for new jobs
        /// </summary>
        private Thread scheduleThread;

        /// <summary>
        /// Thread which updates the processed jobs
        /// </summary>
        private Thread scheduleUpdaterThread;

        /// <summary>
        /// Notify icon which is available for the user to interact in the Windows Task bar tray and to notify if there is any new job started executing
        /// </summary>
        private NotifyIcon notifyIconExecutor;

        /// <summary>
        /// ContextMenu for the tray icon.
        /// </summary>
        private ContextMenuStrip menuStripExecutorContextMenu;

        /// <summary>
        /// ContextMenu item to enable Console for the loaded job
        /// </summary>
        private ToolStripMenuItem mnuItemShowConsole;

        /// <summary>
        /// ContextMenu item to get the about window
        /// </summary>
        private ToolStripMenuItem mnuItemShowAbout;

        /// <summary>
        /// ContextMenu item to exit or quit the Executor
        /// </summary>
        private ToolStripMenuItem mnuItemExitToolStrip;

        /// <summary>
        /// About window
        /// </summary>
        private AboutBox aboutWindow;

        #endregion

        #region Public Methods

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            this.notifyIconExecutor = new NotifyIcon();
            this.notifyIconExecutor.Icon = Properties.Resources.WFServiceTrayApp;
            this.notifyIconExecutor.Visible = true;
            this.notifyIconExecutor.Text = Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.TrayAppTitle;
            this.notifyIconExecutor.DoubleClick += new EventHandler(ShowAboutWindow);

            this.menuStripExecutorContextMenu = new ContextMenuStrip();
            this.mnuItemShowConsole = new ToolStripMenuItem(Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.ShowExecutionConsoleLabel);
            this.mnuItemShowAbout = new ToolStripMenuItem(Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.AboutTridentExecutorLabel);
            this.mnuItemShowAbout.Font = new Font(DEFAULT_FONT, 9F, System.Drawing.FontStyle.Bold);
            this.mnuItemExitToolStrip = new ToolStripMenuItem(Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.ExitLabel);
            this.notifyIconExecutor.ContextMenuStrip = this.menuStripExecutorContextMenu;

            this.mnuItemShowConsole.Click += new EventHandler(showConsoleToolStripMenuItem_Click);
            this.mnuItemShowAbout.Click += new EventHandler(ShowAboutWindow);
            this.mnuItemExitToolStrip.Click += new EventHandler(exitToolStripMenuItem_Click);

            this.menuStripExecutorContextMenu.Items.Add(this.mnuItemShowAbout);
            this.menuStripExecutorContextMenu.Items.Add(this.mnuItemShowConsole);
            this.menuStripExecutorContextMenu.Items.Add(this.mnuItemExitToolStrip);

            this.LoadWFService();
        }

        /// <summary>
        /// Entry point for the Executor
        /// </summary>
        [STAThread]
        public static void Main(string[] args)
        {
            // Create the single instance manager.
            SingleInstanceManager manager = new SingleInstanceManager();
            manager.Run(args);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Handles the Loaded event of the Window control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.Hide();
        }

        /// <summary>
        /// Shutdown the service when the scheduler is terminated
        /// </summary>
        /// <param name="message">The message.</param>
        private void scheduler_OnTerminateScheduler(string message)
        {
            System.Windows.MessageBox.Show(message + "\n\r" + Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.TrayAppErrorCloseConfirmationMessage, Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.TrayAppTitle, MessageBoxButton.OK, MessageBoxImage.Stop);

            Process process = Process.GetCurrentProcess();
            process.CloseMainWindow();

            ShutdownService();
        }

        /// <summary>
        /// Handles the OnLaunchWorkflow event of the scheduler control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="Microsoft.Research.eResearch.TridentScheduleCommon.WFLauchEventArgs"/> instance containing the event data.</param>
        private void scheduler_OnLaunchWorkflow(object sender, WFLauchEventArgs args)
        {
            notifyIconExecutor.ShowBalloonTip(BALLOON_TOOLTIP_TIMEOUT,
                string.Format(Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.WorkflowLaunchedMessage),
                string.Format(Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties.WFServiceTrayApp.LaunchWorkflowMessage, args.WorkflowName),
                ToolTipIcon.Info);
        }

        /// <summary>
        /// Handles the Click event of the exitToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShutdownService();
        }

        /// <summary>
        /// Handles the Click event of the showConsoleToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void showConsoleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            mnuItemShowConsole.Checked = !mnuItemShowConsole.Checked;
            scheduler.ShowConsole = mnuItemShowConsole.Checked;
        }

        /// <summary>
        /// Shows the about window
        /// </summary>
        private void ShowAboutWindow(object sender, EventArgs e)
        {
            ////If the AboutWindow is not initialized or AboutWindow is initialized but closed/invisible
            if ((this.aboutWindow == null) || (this.aboutWindow != null && this.aboutWindow.IsVisible == false))
            {
                this.aboutWindow = new AboutBox();
            }
            ////If it is not visible bring it into view
            if (this.aboutWindow.IsVisible == false)
            {
                this.aboutWindow.ShowDialog();
            }
            ////Already AboutWindow is visble but user is selecting again, just activate the window
            else
            {
                this.aboutWindow.Activate();
            }
        }

        /// <summary>
        /// This method spawns a new Thread which will poll
        /// the registry to look for new WF's to run. 
        /// </summary>
        private void LoadWFService()
        {
            Log("Info", "Attempting to start WF Execution Tray App...");

            Environment.CurrentDirectory = System.AppDomain.CurrentDomain.BaseDirectory;

            InitWorkflowScheduler();

            InitTaskCreator();

            Log("Info", "WF Execution Tray App started successfully.");
        }

        /// <summary>
        /// Inits the windows task creator.
        /// </summary>
        private void InitTaskCreator()
        {
            Log("Info", "Attempting to start windows task creator...");

            try
            {
                // Create WindowsTaskCreater thread
                WindowsTaskCreator taskCreater = new WindowsTaskCreator();
                string SchedulerPollTime = ConfigurationManager.AppSettings["SchedulerPollTimeInMilliseconds"];

                taskCreater.SchedulerEXEPath =
                    System.IO.Path.Combine(Environment.CurrentDirectory, ConfigurationManager.AppSettings["SchedulerEXE"]);

                taskCreater.HostName = scheduler.HostName;
                taskCreater.IsInteractiveExecution = true;
                if (!string.IsNullOrEmpty(SchedulerPollTime))
                    taskCreater.SchedulerPollTime = Int32.Parse(SchedulerPollTime);

                taskCreater.LogMessageReceived += new EventHandler<LogEventArgs>(this.OnLogMessageReceived);

                // Start New thread for updating scheduler information.
                this.scheduleUpdaterThread = new Thread(taskCreater.RunScheduleCreationThread);
                //this.scheduleUpdaterThread.IsBackground = true;
                this.scheduleUpdaterThread.Start();
            }
            catch (Exception ex)
            {
                Log("Error", ex.ToString());
            }

            Log("Info", "Windows task creator started successfully.");
        }

        /// <summary>
        /// Inits the workflow scheduler.
        /// </summary>
        private void InitWorkflowScheduler()
        {
            Log("Info", "Attempting to start workflow scheduler service...");

            try
            {
                // Configure the Scheduler with the details.
                scheduler = new WFServiceScheduler();
                scheduler.HostName = System.Environment.MachineName;
                scheduler.WFProcessName = ConfigurationManager.AppSettings[WORKFLOW_PROCESS_NAME];
                scheduler.ShowConsole = mnuItemShowConsole.Checked;
                scheduler.OnLaunchWorkflow += new EventHandler<WFLauchEventArgs>(scheduler_OnLaunchWorkflow);
                scheduler.OnTerminateScheduler += new WFServiceScheduler.TerminateSchedulerEvent(scheduler_OnTerminateScheduler);
                scheduler.LogMessageReceived += new EventHandler<LogEventArgs>(this.OnLogMessageReceived);
                scheduler.IsInteractiveExecution = true;

                string attempts = ConfigurationManager.AppSettings[CONFIGURATION_ATTEMPT_COUNT];
                if (!string.IsNullOrEmpty(attempts))
                {
                    int reconnectAttempt = 0;
                    Int32.TryParse(attempts, out reconnectAttempt);
                    scheduler.configurableReconnectAttempts = reconnectAttempt;
                }

                // Start New thread for Running the pending workflows.
                this.scheduleThread = new Thread(scheduler.RunWorkflows);
                //this.scheduleThread.IsBackground = true;
                this.scheduleThread.Start();
            }
            catch (Exception ex)
            {
                Log("Error", ex.ToString());
            }

            Log("Info", "Workflow scheduler service started successfully.");
        }

        /// <summary>
        /// Shutdowns the service.
        /// </summary>
        private void ShutdownService()
        {
            try
            {
                // Stop the WF polling thread.
                if (null != this.scheduleUpdaterThread && this.scheduleUpdaterThread.IsAlive)
                {
                    this.scheduleUpdaterThread.Abort();
                }

                if (null != this.scheduleThread && this.scheduleThread.IsAlive)
                {
                    this.scheduleThread.Abort();
                }
            }
            catch (Exception ex)
            {
                Log("Error", ex.Message);
            }
            finally
            {
                Log("Info", "Exiting the Tray App");

                if (this.notifyIconExecutor != null)
                {
                    this.notifyIconExecutor.Visible = false;
                }

                Environment.Exit(0);
            }
        }

        /// <summary>
        /// this function is used to log the message.
        /// </summary>
        /// <param name="level">Level.</param>
        /// <param name="message">Message to be logged.</param>
        private void Log(string level, string message)
        {
            Logger.Write(message, "FileLogger");
        }

        /// <summary>
        /// Log message received event handler.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnLogMessageReceived(object sender, LogEventArgs e)
        {
            this.Log(e.Level, e.Message);
        }

        #endregion
    }

    # region SingleInstanceManager

    // Using VB	bits to	detect single instances	and	process	accordingly:
    //	* OnStartup	is fired when the first	instance loads
    //	* OnStartupNextInstance	is fired when the application is re-run	again
    //	  NOTE:	it is redirected to	this instance thanks to	IsSingleInstance
    public class SingleInstanceManager : WindowsFormsApplicationBase
    {
        System.Windows.Application app;

        /// <summary>
        /// Initializes a new instance of the <see cref="SingleInstanceManager"/> class.
        /// </summary>
        public SingleInstanceManager()
        {
            this.IsSingleInstance = true;
        }

        /// <summary>
        /// Raises the <see cref="E:Startup"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.VisualBasic.ApplicationServices.StartupEventArgs"/> instance containing the event data.</param>
        /// <returns></returns>
        protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs e)
        {
            // First time app is launched
            app = new System.Windows.Application();
            app.Run(new MainWindow());
            return false;
        }

        /// <summary>
        /// When overridden in a derived class, allows for code to run when a subsequent instance of a single-instance application starts.
        /// </summary>
        /// <param name="eventArgs"><see cref="T:Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs"/>. Contains the command-line arguments of the subsequent application instance and indicates whether the first application instance should be brought to the foreground upon exiting the exception handler.</param>
        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
        {
            // Subsequent launches
            base.OnStartupNextInstance(eventArgs);
            app.MainWindow.Activate();
        }
    }

    #endregion
}
